package com.ws.demo.inorderTraversal;

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

public class InorderTraversalDemo {

    /**
     * 给定一个二叉树的根节点 root ，返回 它的 中序 遍历 。
     */

    /**
     * 递归
     *
     * 首先我们需要了解什么是二叉树的中序遍历：按照访问左子树——根节点——右子树的方式遍历这棵树，
     * 而在访问左子树或者右子树的时候我们按照同样的方式遍历，直到遍历完整棵树。
     * 因此整个遍历过程天然具有递归的性质，我们可以直接用递归函数来模拟这一过程。
     *
     * 定义 inorder(root) 表示当前遍历到 root 节点的答案，那么按照定义，
     * 我们只要递归调用 inorder(root.left) 来遍历 root 节点的左子树，
     * 然后将 root 节点的值加入答案，再递归调用inorder(root.right) 来遍历 root 节点的右子树即可，
     * 递归终止的条件为碰到空节点。
     */
//    public List<Integer> inorderTraversal(TreeNode root) {
//        List<Integer> res = new ArrayList<Integer>();
//        inorder(root, res);
//        return res;
//    }
//
//    public void inorder(TreeNode root, List<Integer> res) {
//        if (root == null) {
//            return;
//        }
//        inorder(root.left, res);
//        res.add(root.val);
//        inorder(root.right, res);
//    }


    /**
     * 递归
     */
//    public List<Integer> inorderTraversal(TreeNode root) {
//        List<Integer> res = new ArrayList<Integer>();
//        Deque<TreeNode> stk = new LinkedList<TreeNode>();
//        while (root != null || !stk.isEmpty()) {
//            while (root != null) {
//                stk.push(root);
//                root = root.left;
//            }
//            root = stk.pop();
//            res.add(root.val);
//            root = root.right;
//        }
//        return res;
//    }

    /**
     * Morris 中序遍历
     *Morris 遍历算法是另一种遍历二叉树的方法，它能将非递归的中序遍历空间复杂度降为 O(1)O(1)O(1)。
     *
     * Morris 遍历算法整体步骤如下（假设当前遍历到的节点为 xxx）：
     *
     * 1.如果 xxx 无左孩子，先将 xxx 的值加入答案数组，再访问 xxx 的右孩子，即 x=x.right = x.right。
     * 2.如果 xxx 有左孩子，则找到 xxx 左子树上最右的节点（即左子树中序遍历的最后一个节点，xxx 在中序遍历中的前驱节点），我们记为 predecessor。根据 predecessor 的右孩子是否为空，进行如下操作。
     *  - 如果 predecessor 的右孩子为空，则将其右孩子指向 xxx，然后访问 xxx 的左孩子，即 x=x.left = x.left。
     *  - 如果 predecessor 的右孩子不为空，则此时其右孩子指向 xxx，说明我们已经遍历完 xxx 的左子树，我们将 predecessor 的右孩子置空，将 xxx 的值加入答案数组，然后访问 xxx 的右孩子，即 x=x.right = x.right。
     * 3.重复上述操作，直至访问完整棵树。
     */
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        TreeNode predecessor = null;

        while (root != null) {
            if (root.left != null) {
                // predecessor 节点就是当前 root 节点向左走一步，然后一直向右走至无法走为止
                predecessor = root.left;
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }

                // 让 predecessor 的右指针指向 root，继续遍历左子树
                if (predecessor.right == null) {
                    predecessor.right = root;
                    root = root.left;
                }
                // 说明左子树已经访问完了，我们需要断开链接
                else {
                    res.add(root.val);
                    predecessor.right = null;
                    root = root.right;
                }
            }
            // 如果没有左孩子，则直接访问右孩子
            else {
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode() {}
        TreeNode(int val) { this.val = val; }
        TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }
}
